package com.sangeng.service.impl;

import com.sangeng.domain.LoginRequest;
import com.sangeng.domain.LoginUser;
import com.sangeng.domain.ResponseResult;
import com.sangeng.entity.User;
import com.sangeng.exception.UserCheckException;
import com.sangeng.service.LoginServcie;
import com.sangeng.utils.JwtUtil;
import com.sangeng.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author: xujiabing
 * @date: 2024-09-13 18:24
 * @description 登录业务层
 */
@Service
public class LoginServcieImpl implements LoginServcie {

    /**
     * 定义了认证Authentication的方法
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    /**
     * 设置token 在redis中的过期时间 30分钟
     */
    private static final Integer EXPIRY_TIME = 30;

    private final String CAPTCHA_KEY = "captcha:verification:";

    /**
     * 输入用户名或者密码最大次数
     */
    private static final int MAX_ATTEMPTS = 5;

    /**
     * 超过次数禁用时间
     */
    private static final Integer LOCK_TIME_DURATION = 15 * 60 * 1000; // 15 minutes in millisecond

    /**
     * 用户登录实现
     * @param user
     * @return
     */
    @Override
    public ResponseResult login(User user) {
        //1.authenticationManager authenticate 进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //如果为null,认证没通过
        if (Objects.isNull(authenticate)) {
            throw new UserCheckException("登录失败");
        }

        //2.如果认证通过,使用userId生成jwt,返回
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        //生成token信息
        String jwt = JwtUtil.createJWT(userId);

        //3.将登录用户信息存放在redis中,userId为key
        redisCache.setCacheObject("login:" + userId , loginUser, EXPIRY_TIME, TimeUnit.MINUTES);

        //返回信息
        Map<String, String> map = new HashMap<>();
        map.put("token",jwt);
        return new ResponseResult(200,"登录成功",map);
    }

    /**
     * 登录实现 + 验证码
     * @param loginRequest
     * @return
     */
    @Override
    public ResponseResult loginWithCaptcha(LoginRequest loginRequest) {
        // todo 新增验证码校验
        String captchaUUID = loginRequest.getcaptchaUUID();
        String captcha = loginRequest.getCaptcha();

        if (!StringUtils.hasText(captchaUUID)) {
            throw new UserCheckException("验证码为空");
        }
        Object cacheObject = redisCache.getCacheObject(CAPTCHA_KEY.concat(captchaUUID));
        if (Objects.isNull(cacheObject)) {
            throw new UserCheckException("验证码已失效");
        }
        // 验证码文本内容
        String captchaValue = cacheObject.toString();
        if (!captchaValue.equalsIgnoreCase(captcha)) {
            redisCache.deleteObject(CAPTCHA_KEY.concat(captchaUUID));
            throw new UserCheckException("验证码错误");
        }

        // 验证码校验通过，删除redis验证码
        redisCache.deleteObject(CAPTCHA_KEY.concat(captchaUUID));

        // todo 增加输入用户名或密码错误次数限制

        if (isAccountLocked(loginRequest.getUsername())) {
            throw new UserCheckException("登录失败,账户已锁定,请于15分钟后尝试");
        }

        //1.authenticationManager authenticate 进行用户认证
        Authentication authenticate = null;
        try {
            UsernamePasswordAuthenticationToken authenticationToken
                    = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
            authenticate = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            System.out.println("用户名或者密码错误");
            incrementFailedAttempts(loginRequest.getUsername());
            int attempts = getFailedAttempts(loginRequest.getUsername());
            throw new UserCheckException("用户名或密码错误. 还有 " + (MAX_ATTEMPTS - attempts) + " 次机会, 超过会被锁定15分钟");
        }
        //如果为null,认证没通过
        if (Objects.isNull(authenticate)) {
            throw new UserCheckException("登录失败");
        }
        // 重置登录错误次数
        resetFailedAttempts(loginRequest.getUsername());

        //2.如果认证通过,使用userId生成jwt,返回
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        //生成token信息
        String jwt = JwtUtil.createJWT(userId);

        //3.将登录用户信息存放在redis中,userId为key
        redisCache.setCacheObject("login:" + userId , loginUser, EXPIRY_TIME, TimeUnit.MINUTES);

        //返回信息
        Map<String, String> map = new HashMap<>();
        map.put("token",jwt);
        return new ResponseResult(200,"登录成功",map);
    }


    /**
     * 推出登录实现
     * 每次请求是不同的 SecurityContextHolder
     *  ,下次请求JwtAuthenticationTokenFilter
     *  不会将 authenticationToken 放入到 SecurityContextHolder 中
     * @return
     */
    @Override
    public ResponseResult logout() {
        //1.获取到 SecurityContextHolder,取出userId
        Authentication authentication =
                SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        //获取userId
        String userId = loginUser.getUser().getId().toString();
        //2.删除redis缓存信息
        redisCache.deleteObject("login:" + userId);
        return new ResponseResult(200,"退出成功");
    }

    /**
     * 更新失败次数
     * @param username
     */
    private void incrementFailedAttempts(String username) {
        String key = "login:failedAttempts:" + username;
        Integer attempts = (Integer) redisCache.getCacheObject(key);
        if (attempts == null) {
            attempts = 0;
        }
        attempts++;
        redisCache.setCacheObject(key, attempts, LOCK_TIME_DURATION, TimeUnit.MILLISECONDS);
    }

    /**
     * 重置用户失败次数
     * @param username
     */
    private void resetFailedAttempts(String username) {
        String key = "login:failedAttempts:" + username;
        redisCache.deleteObject(key);
    }

    /**
     * 查询用户是否已经被锁定
     * @param username
     * @return
     */
    private boolean isAccountLocked(String username) {
        String key = "login:failedAttempts:" + username;
        Integer attempts = (Integer) redisCache.getCacheObject(key);
        return attempts != null && attempts >= MAX_ATTEMPTS;
    }

    /**
     * 查询用户登录失败次数
     * @param username
     * @return
     */
    private int getFailedAttempts(String username) {
        String key = "login:failedAttempts:" + username;
        Integer attempts = (Integer) redisCache.getCacheObject(key);
        return attempts != null ? attempts : 0;
    }
}
